﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Workflows.Components.Entities.Distances.VectorDistances;
using Workflows.Components.Entities.Documents.Terms;

namespace Workflows.Components.Entities.Documents.Distances
{
    public class EuclideanDistanceCalculator : IFeatureVectorDistanceCalculator
    {
        #region IAttributeComparer Members
        //public string AssemblyFileName
        //{
        //    get { return this.GetType().Assembly.GetName().Name; }
        //    set { }
        //}

        //public string TypeFullName
        //{
        //    get { return this.GetType().FullName; }
        //    set { }
        //}

        public double CalculateDistance(object attr1, object attr2)
        {
            if (attr1.GetType() != typeof(BagOfWords) || attr2.GetType() != typeof(BagOfWords))
                throw new Exception("The attribute must be of type BagOfWords");

            BagOfWords termFreq1 = (BagOfWords)attr1;
            BagOfWords termFreq2 = (BagOfWords)attr2;
            if (termFreq1 == null || termFreq2 == null ||
                termFreq1.TermFreqs == null || termFreq2.TermFreqs == null ||
                termFreq1.TermFreqs.Count == 0 || termFreq2.TermFreqs.Count == 0)
                return double.PositiveInfinity;

            List<int> unionTermIDs = new List<int>();
            foreach (int termID1 in termFreq1.TermFreqs.Keys)
            {
                unionTermIDs.Add(termID1);
            }
            foreach (int termID2 in termFreq2.TermFreqs.Keys)
            {
                if (!unionTermIDs.Contains(termID2))
                {
                    unionTermIDs.Add(termID2);
                }
            }
            double sumSq = 0;
            Dictionary<int, double> commonTerms = new Dictionary<int, double>();
            foreach (int termID in unionTermIDs)
            {
                if (termFreq1.TermFreqs.ContainsKey(termID) && termFreq2.TermFreqs.ContainsKey(termID))
                {
                    commonTerms.Add(termID, Math.Min(termFreq1.TermFreqs[termID], termFreq2.TermFreqs[termID]));
                    sumSq += 0;
                }
                else
                {
                    double freq1 = 0;
                    if (termFreq1.TermFreqs.ContainsKey(termID))
                        freq1 = termFreq1.TermFreqs[termID] * termFreq1.TermFreqs[termID];
                    double freq2 = 0;
                    if (termFreq2.TermFreqs.ContainsKey(termID))
                        freq2 = termFreq2.TermFreqs[termID] * termFreq2.TermFreqs[termID];
                    sumSq += Math.Pow(freq1 - freq2, 2);
                }
            }
            double docLenScale = (double)commonTerms.Count * 2 /
                                 (termFreq1.TermFreqs.Count + termFreq2.TermFreqs.Count - commonTerms.Count);
            double euclideanDistance = Math.Sqrt(sumSq) * docLenScale;

            double contributingTermScale =
                TermWeightCalculator.TaminoWeightedTermScale(
                    termFreq1, termFreq2,
                    commonTerms);

            return euclideanDistance * contributingTermScale;
        }

        #endregion
    }
}
